home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Precision Software Appli…tions Silver Collection 1
/
Precision Software Applications Silver Collection Volume One (PSM) (1993).iso
/
tutor
/
asm1tut.exe
/
CHAP12.DOC
< prev
next >
Wrap
Text File
|
1990-06-25
|
18KB
|
443 lines
122
CHAPTER 12 - MULTIPLE WORD ARITHMETIC I
Let's review the LOOP instruction. We often want to repeat an
action a specific number of times. In a FOR loop, we write:
FOR I = 1, 10
That means we want to repeat the code that follows ten times. The
8086 has an instruction for this, called the loop instruction. It
looks like this:
mov cx, 10
label17:
...
(a bunch of code)
...
loop label17
The count MUST be in the CX register. This is the only register
you can use for this. When the machine sees the loop instruction,
it decrements the CX register by one, LEAVING ALL FLAGS ALONE,
and if the result is not zero, it loops back to the label. If the
result is zero, it falls through the loop. One problem we might
have with this instruction is if you enter it with CX = 0, it is
going to loop 65,536 times. Intel provided a second instruction
to avoid this - JCXZ (jump if CX is zero). You put it right
before the loop for insurance.
jcxz label19
label17:
...
(a bunch of code)
...
loop label17
label19:
Obviously, in our first example this instruction is not necessary
because we set CX to 10 just before entering the loop.
If you have seen the list of 8086 instructions, you will have
noticed lots of strange looking add and subtract instructions.
Why are they there? In this chapter we will look at ADC and SBB.
The others will come in later chapters.
How do engineers decide what a reasonable size for a number is?
When they started making 8 bit machines (the maximum unsigned
number is 255) did they go out on the streets and take a poll to
find out if 255 was the maximum number that people used? No,
they didn't even think about what people needed. It was a
question of what the technology would allow at that time.
______________________
The PC Assembler Tutor - Copyright (C) 1989 Chuck Nelson
Chapter 12 - Multiple Word Arithmetic I 123
_______________________________________
Similarly, at the time the 8086 was engineered, 16 bits was
pushing the limits of the technology. But 65,535 doesn't really
cut the mustard. If those are 65,535 pennies, that isn't even
your yearly food bill, let alone the cost of housing.
The 8086 instructions give us the option of making integers of
whatever size we want. Because it is done in software, it is
slower, but if we are doing thousands or tens of thousands of
additions instead of hundreds of thousands or millions, it won't
be a great inconvenience.{1}
ASMHELP.OBJ is set up to input 2 word (4 byte) and 4 word (8
byte) numbers so those are what we are going to use. 4 byte
numbers are up to +/- 2 billion and 8 byte numbers are up to
9X10exp18. Those should be large enough.
The first instruction is ADC, add with carry. When you add by
hand, you add everything in the right column, then carry to the
next column left, repeating this over and over. We don't need to
do it column by column, but it is necessary to do it word by
word. In the data section we need:
variable1 dq ?
variable2 dq ?
and the code for a 4 word (8 byte) addition is the following:
lea si, variable1
lea di, variable2
mov ax, [di] ; first addition
add [si], ax
pushf ; save the flags
mov cx, 3
add si, 2 ; go to next higher word
add di, 2
long_add_loop: ; next three additions
mov ax, [di]
popf ; restore the flags
adc [si], ax
pushf ; save the new flags
add di, 2
add si, 2
loop long_add_loop
popf ; pop the flags off the stack
ADC is the same as ADD, but it looks at the carry flag - if the
carry flag is 1, it adds 1 to the result; if the carry flag is
zero, it does nothing to the result. This works for both signed
and unsigned numbers. If you don't believe it, you should go back
____________________
1 As a benchmark, it took a moderately slow PC 6.5 seconds to
do 100,000 eight byte additions. The same PC can do over a
million two byte additions in 6 seconds.
The PC Assembler Tutor 124
______________________
to the introductory chapter with the base 10 machine and look at
long additions.
Notice PUSHF and POPF. These are special instructions called push
flags and pop flags. Rather than pushing an arithmetic register
on the stack, pushf pushes the register containing the flags.
Popf pops them back into the flags register. This is necessary
because ADC looks at the carry flag and the ADD instructions in
the loop:
add di,2
add si,2
might change the carry flag. The last POPF after the loop is to
get it off the stack (anything we put on the stack we need to
take off the stack).
The first addition is a normal addition, the last three take the
carry into account. We are moving the pointers a word at a time.
Because the 8086 doesn't allow both operands to be in memory, we
need to move one into a register. After the addition is
performed, the result is in memory. We can discard what is in the
register.
Notice that the first half of the code looks almost the same as
the code inside the loop. If we could only use ADC instead of
ADD, we could put the first addition inside the loop. It is
possible to do this. There is another instruction, CLC, which
clears the carry flag. Recall that if the carry flag is 0, ADC
does nothing different from ADD. Therefore, we can have:
lea si, variable1
lea di, variable2
mov cx, 4 ; 4 additions in loop
clc ; set cf to zero
pushf ; save the flags
long_add_loop:
mov ax, [di] ; word to a register
popf ; restore the flags
adc [si], ax ; register + memory
pushf ; save the flags
add di, 2
add si, 2
loop long_add_loop
popf ; pop the flags off the stack
It's the same code. The number of loops was increased from 3 to
4, and the carry flag was cleared to insure that the first
addition would have nothing extra added. Here is the basic
program.
; - - - - - PUT DATA BELOW THIS LINE
variable1 dq ?
variable2 dq ?
; - - - - - PUT DATA ABOVE THIS LINE
Chapter 12 - Multiple Word Arithmetic I 125
_______________________________________
; - - - - - PUT CODE BELOW THIS LINE
call show_regs
outer_loop:
lea ax, variable1 ; get the variables
call get_signed_8byte
call print_signed_8byte
lea ax, variable2
call get_signed_8byte
call print_signed_8byte
lea si, variable1 ; set the pointers
lea di, variable2
mov cx, 4 ; loop 4 times (4 words)
clc ; clear the cf
pushf ; save the flags
long_add_loop:
mov ax, [di] ; word to a register
popf ; restore the flags
adc [si], ax ; register + memory
pushf ; save the flags
add di, 2
add si, 2
loop long_add_loop
popf ; restore the flags
lea ax, variable1
call print_signed_8byte
jmp outer_loop
; - - - - - PUT CODE ABOVE THIS LINE
The calls to get_signed_8byte are followed by print_signed_8byte.
This is so you can see what you have actually typed in. It will
be neat and with commas, so it will be much easier to read.
Everything else is the same as before.
As an aside, let's talk about commas. Though we can get along
without commas if we have a 4 digit number, There is no reason to
do without them when using larger numbers. I find printer output
that has 15 digit floating point numbers without commas not only
hard to read but also silly. It's not that the computer is
incapable of putting in commas, it's that the prople who wrote
the programs couldn't be bothered with doing 2 hours of work to
save the users lots and lots of aggrivation. Therefore, for all
large number input (that is, larger than 1 word) you can use
commas. They don't even have to be in the right place, the
computer ignores them. All the following are the same number:
723469746
723,4,69746
72,3,46974,6
The PC Assembler Tutor 126
______________________
7,2,3,4,6,9,7,4,6
723,469,746
The program strips all commas out of the line and then looks at
the input. All large output has the commas in the right spot. In
order to enlist you in the crusade to stamp out unreadable input
and output, the summary at the end of this chapter has the C code
necessary for stripping commas. This is my contribution to world
culture.
If you have played with the signed addition program, all we need
to do to make it unsigned is to change all the get_signed_8byte
calls to get_unsigned_8byte calls. Change the print calls to
unsigned also. Run the program.
Now, let's try subtraction. How much code do we have to alter to
make it a subtraction program? The answer is - one word. Just
change the ADC (add with carry) to SBB (subtract with borrow).
SBB learns from the CF flag whether the last subtraction had to
borrow, and tells the next subtraction via the CF flag whether it
has had to borrow. Change it, and try it out. (Yes, really do it,
don't just pretend that you are going to do it).
Why does this program work with exactly 8 bytes? Because we tell
the loop via CX that it is 4 words long. If we put 2 in CX, the
loop will think that the number is 2 words (4 bytes) long, and if
we put 17 in CX, the loop will think that the number is 17 words
(34 bytes) long. In fact, this little snippet of code can do
either signed or unsigned addition or subtraction of any number
of words simply by altering one number and one word of code.
The code as it stands has only one shortfall. Remember from our
earlier subtraction that we might want to have an INTO (interrupt
on overflow) instruction after signed addition and subtraction.
It needs to be after the last addition (or subtraction). All we
need to do is put it after the POPF just below the loop. At this
point the flags show the condition right after the last addition
or subtraction:
loop long_add_loop
popf ; pop the flags off the stack
into ; interrupt if overflow is set
Chapter 12 - Multiple Word Arithmetic I 127
_______________________________________
SUMMARY
ADC (add with carry) is used for multiple word arithmetic. It
adds two numbers along with plus the value in CF, the carry flag
(1 if the flag is set and 0 if the flag is cleared). CLC (clear
the carry flag) should be used to clear CF before the first
addition. After the addition, the flags register should be pushed
in order to store CF unless it is certain that CF will not be
effected by the rest of the loop.
popf
adc [di], ax
pushf
SBB (subtract with borrow) is used for multiple word arithmetic.
It subtracts one number from the other and subtracts the value in
CF to account for any borrows from the right. CLC (clear the
carry flag) should be used to clear CF before the first
subtraction. After the subtraction, the flags register should be
pushed in order to store CF unless it is certain that CF will not
be effected by the rest of the loop.
popf
sbb [di], ax
pushf
These operations have the typical 5 possibilities:
1) register, register
2) register, memory
3) memory, register
4) memory, constant
5) register, constant
You may manually control the value in CF with STC and CLC. STC
(set the carry flag) sets the value to 1, while CLC (clear the
carry flag) sets the value to 0. These are used for initial
settings of multiple word operations.
In order to store the values in the flags register you use PUSHF
(push the flags) until you need them again. At that time you can
get them back with POPF (pop the flags).
The PC Assembler Tutor 128
______________________
STRIPPING COMMAS IN C
In order to get rid of commas, you need some discipline in what
kind of data entry you have. Specifically, you can't enter large
numbers on the same line as text strings because text strings are
likely to have commas that should be kept. This is hardly a major
restriction. You can have as many numbers as you want on the same
line since in C you must have whitespace between pieces of data.
We will take all commas out of the line. You can retrofit most
old programs with almost no change.
The only time that you need this capability is if you are getting
something from the keyboard, and what you probably have in the
program is:
scanf ( "format string", variables );
The method is (1) import a text string as a single string, (2)
strip the commas, and (3) use sscanf instead of scanf.
char buffer[80] ;
fgets (buffer, 80, stdin) ;
strip_commas (buffer);
sscanf (buffer, "format string", variables) ;
Both "format string" and the variables remain unchanged when you
switch from scanf to sscanf. You might want to check for EOF with
fgets.
Heres the program:
strip_commas (buffer)
char *buffer ;
{
char *ptr1, *ptr2 ;
ptr1 = ptr2 = buffer ;
while (1)
{
if ( *ptr2 == ',') /* skip commas */
{
ptr2++ ;
continue ;
}
/*move, increment, and check for 0 */
if (!(*ptr1++ = *ptr2++)) /* this is '=', not '==' */
break ;
}
return ;
}